Option Explicit
' この VBA プログラムは、Excel の .xlsm ファイルにて、
' ボタンから呼び出す形で使用します。
' シート上で、.js に変換したい図形を選択してから
' そのボタンを押すと、
' その図形の名前の .js ファイルが作成されます。
' step3 drawShape.html 内にて、
' <script src="step2 shape0.js"></script>
' のファイル名部分を書き換えれば、JavaScript で
' 描画されるはずです。
' outputShapeToJS() から参照されている
Const shapeTypes_ As String = "dummy msoAutoShape msoCallout msoChart msoComment msoFreeform msoGroup msoEmbeddedOLEObject msoFormControl msoLine msoLinkedOLEObject msoLinkedPicture msoOLEControlObject msoPicture msoPlaceholder msoTextEffect msoMedia msoTextBox msoScriptAnchor msoTable msoCanvas msoDiagram msoInk msoInkComment msoSmartArt msoSlicer"
Const editingTypes_ As String = "msoEditingAuto msoEditingCorner msoEditingSmooth msoEditingSymmetric"
Const segmentTypes_ As String = "msoSegmentLine msoSegmentCurve"
Public OUT As Object
Sub 図形をJSへ出力_Click()
'図をそれぞれ FreeForm に変換する
'check.
If Not VarType(Selection) = vbObject Then
MsgBox "図形が選択されていない"
End
End If
Dim theShape As Shape
Set theShape = Selection.ShapeRange(1) '''
'check. 変換できない図形かどうか事前確認
checkShape theShape
'check. ダブルクオートは削除
theShape.Name = Replace(theShape.Name, """", "")
'check. コロンは全角
theShape.Name = Replace(theShape.Name, ":", ":")
'check.
bugfixShape theShape
'書き込み準備
Set OUT = CreateObject("ADODB.Stream") '''
OUT.Charset = "UTF-8"
OUT.LineSeparator = adLF
OUT.Open
OUTPUT "if( typeof shapez === ""undefined"" ) shapez = new Object();"
OUTPUT "if( typeof shapes === ""undefined"" ) shapes = new Array();"
' Application.ScreenUpdating = False
Dim freeForm As Shape
Set freeForm = convertToFreeform(theShape) ' FreeForm へ変換し、'''
outputShapeToJS freeForm, 0, False ' FreeForm を元に JavaScript を出力
Selection.Delete ' FreeForm を削除
Set freeForm = Nothing
theShape.Select ' 元の図形を選択
OUT.SaveToFile ActiveWorkbook.Path & "\" & theShape.Name & ".js", 2
OUT.Close
Set OUT = Nothing
Debug.Print vbCr & "====="
Set theShape = Nothing
End Sub
'=== 主要
Function convertToFreeform(theShape As Shape, Optional tabs As String = "", Optional history As String = "") As Shape
If theShape.Type = msoGroup Then
'--- グループの場合
Dim originalLeft As Single
Dim originalTop As Single
originalLeft = theShape.Left
originalTop = theShape.Top
Dim maxSize As Single
If theShape.Width > theShape.Height Then
maxSize = theShape.Width
Else
maxSize = theShape.Height
End If
If theShape.Left < maxSize / 2 Then
theShape.Left = maxSize / 2
End If
If theShape.Top < maxSize / 2 Then
theShape.Top = maxSize / 2
End If
Debug.Print tabs & "■Group:" & theShape.Name
' 子をリストアップする
' グループ解除する
' グループが含む子をリストアップする
Dim theShapeIDBak As String: theShapeIDBak = theShape.id
Dim theShapeNameBak As String: theShapeNameBak = theShape.Name
Dim groupItems() As Shape
groupItems = groupToArray(theShape)
' 後に ungroup されることで、child や parentGroup などのプロパティに不都合が起こる。
' かもしれない。
' グループ解除する
theShape.Ungroup
Dim freeForms() As Shape
Dim freeFormsCount: freeFormsCount = 0
Dim i As Integer
For i = LBound(groupItems) To UBound(groupItems)
Dim groupItem As Shape
Set groupItem = groupItems(i) '''
Dim freeForm As Shape
If groupItem.child Then
' ungroup したとき、その子はまだ何か別の図形の子である(孫)
Debug.Print tabs & "child:" & groupItem.Name
If InStr(1, history, "[" & groupItem.ParentGroup.id & "]", 1) = 0 Then
history = history & "[" & groupItem.ParentGroup.id & "]"
Set freeForm = convertToFreeform(groupItem.ParentGroup, tabs & vbTab, history) '''
freeForm.Left = groupItem.ParentGroup.Left
freeForm.Top = groupItem.ParentGroup.Top
ReDim Preserve freeForms(freeFormsCount)
Set freeForms(freeFormsCount) = freeForm '''
freeFormsCount = freeFormsCount + 1
End If
Else
Debug.Print tabs & "not child:" & groupItem.Name
' ungroup したとき、その子は単一図形である
Set freeForm = convertToFreeform(groupItem, tabs & vbTab, history) '''
' freeForm.Left = groupItem.Left
' freeForm.Top = groupItem.Top
ReDim Preserve freeForms(freeFormsCount)
Set freeForms(freeFormsCount) = freeForm '''
freeFormsCount = freeFormsCount + 1
End If
Set freeForm = Nothing
Set groupItem = Nothing
Next i
'再グループ化
selectShapes groupItems
Set theShape = Selection.Group.ShapeRange(1) ' これは解放しなくてよい
theShape.Name = theShapeNameBak
' theShape.Select
'check. とはいえ新しいグループだから ID は変わる
history = Replace(history, "[" & theShapeIDBak & "]", "[" & theShape.id & "]")
theShape.Left = originalLeft
theShape.Top = originalTop
selectShapes freeForms
Dim newGroup As Shape
Set newGroup = Selection.ShapeRange.Group '''
newGroup.Left = originalLeft + 12
newGroup.Top = originalTop + 12
newGroup.Select
Set convertToFreeform = newGroup
Erase freeForms
Set newGroup = Nothing
Else
'--- 単一図形の場合
Debug.Print tabs & "●Shape:" & theShape.Name
Dim vFlipBak
vFlipBak = theShape.VerticalFlip
If vFlipBak Then
theShape.Flip (msoFlipVertical)
End If
Dim hFlipBak
hFlipBak = theShape.HorizontalFlip
If hFlipBak Then
theShape.Flip (msoFlipHorizontal)
End If
Dim rotationBak
rotationBak = theShape.Rotation
theShape.Rotation = 0
duplicate theShape
Dim tmpShape As Shape
Set tmpShape = Selection.ShapeRange(1) '''
tmpShape.Left = theShape.Left
tmpShape.Top = theShape.Top
Call tmpShape.Nodes.SetPosition(2, 100, 100)
Dim node1X As Single
Dim node1Y As Single
Dim pointsArray As Variant
pointsArray = tmpShape.Nodes(1).Points
node1X = pointsArray(1, 1)
node1Y = pointsArray(1, 2)
Set tmpShape = Nothing
Selection.Delete
duplicate theShape
Dim newShape As Shape
Set newShape = Selection.ShapeRange(1) '''
newShape.Left = theShape.Left
newShape.Top = theShape.Top
Call newShape.Nodes.SetPosition(1, node1X, node1Y)
pointsArray = newShape.Nodes(1).Points
node1X = pointsArray(1, 1)
node1Y = pointsArray(1, 2)
If rotationBak Then
theShape.Rotation = rotationBak
newShape.Rotation = rotationBak
End If
If hFlipBak Then
theShape.Flip (msoFlipHorizontal)
newShape.Flip (msoFlipHorizontal)
End If
If vFlipBak Then
theShape.Flip (msoFlipVertical)
newShape.Flip (msoFlipVertical)
End If
Set convertToFreeform = newShape
Set newShape = Nothing
Application.ScreenUpdating = False
Application.ScreenUpdating = True
End If
End Function
Sub outputShapeToJS(theShape As Variant, tabCount As Integer, isChild As Boolean)
Dim shapeTypes As Variant: shapeTypes = Split(shapeTypes_, " ")
Dim editingTypes As Variant: editingTypes = Split(editingTypes_, " ")
Dim segmentTypes As Variant: segmentTypes = Split(segmentTypes_, " ")
If isChild Then
OUTPUT tabs(tabCount) & """" & theShape.id & """ : {"
Else
OUTPUT tabs(tabCount) & "shapez[ """ & theShape.id & """ ] = {"
End If
tabCount = tabCount + 1
OUTPUT tabs(tabCount) & "name : """ & theShape.Name & ""","
OUTPUT tabs(tabCount) & "type : """ & shapeTypes(theShape.Type) & ""","
OUTPUT tabs(tabCount) & "left : " & theShape.Left & ","
OUTPUT tabs(tabCount) & "top : " & theShape.Top & ","
OUTPUT tabs(tabCount) & "width : " & theShape.Width & ","
OUTPUT tabs(tabCount) & "height : " & theShape.Height & ","
OUTPUT tabs(tabCount) & "rotation : " & theShape.Rotation & ","
OUTPUT tabs(tabCount) & "zOrder : " & theShape.ZOrderPosition & ","
OUTPUT tabs(tabCount) & "fill : {"
OUTPUT tabs(tabCount + 1) & "visible : " & theShape.Fill.Visible & ","
OUTPUT tabs(tabCount + 1) & "foreColor : {"
OUTPUT tabs(tabCount + 2) & "RGB : " & theShape.Fill.ForeColor.RGB & ","
OUTPUT tabs(tabCount + 1) & "},"
OUTPUT tabs(tabCount) & "},"
OUTPUT tabs(tabCount) & "line : {"
OUTPUT tabs(tabCount + 1) & "visible : " & theShape.Line.Visible & ","
OUTPUT tabs(tabCount + 1) & "foreColor : {"
OUTPUT tabs(tabCount + 2) & "RGB : " & theShape.Line.ForeColor.RGB & ","
OUTPUT tabs(tabCount + 1) & "},"
OUTPUT tabs(tabCount + 1) & "weight : " & theShape.Line.Weight & ","
OUTPUT tabs(tabCount) & "},"
If 0 Then
OUTPUT tabs(tabCount) & "drawingObject : {"
OUTPUT tabs(tabCount + 1) & "interior : {"
OUTPUT tabs(tabCount + 2) & "color : " & theShape.DrawingObject.Interior.Color & ","
OUTPUT tabs(tabCount + 1) & "},"
OUTPUT tabs(tabCount) & "},"
End If
If theShape.Type = msoGroup Then
' グループである
OUTPUT tabs(tabCount) & "groupItemz : {"
Dim childShape As Shape
For Each childShape In theShape.groupItems
outputShapeToJS childShape, tabCount + 1, True
Next childShape
OUTPUT tabs(tabCount) & "}, // groupItemz"
ElseIf theShape.Type = msoFreeform Then
' フリーフォームである
Dim arr As Variant
Dim x, y
OUTPUT tabs(tabCount) & "nodes : [ // " & theShape.Nodes.count
tabCount = tabCount + 1
Dim i As Integer
For i = 1 To theShape.Nodes.count
Dim theNode As ShapeNode
Set theNode = theShape.Nodes(i) '''
OUTPUT tabs(tabCount) & "{ // " & (i - 1)
tabCount = tabCount + 1
On Error Resume Next
Dim tmp As Integer
tmp = -1
tmp = theNode.EditingType
On Error GoTo 0
If tmp = -1 Then
OUTPUT tabs(tabCount) & "editingType : null,"
Else
OUTPUT tabs(tabCount) & "editingType : """ & editingTypes(theNode.EditingType) & ""","
End If
OUTPUT tabs(tabCount) & "segmentType : """ & segmentTypes(theNode.SegmentType) & ""","
arr = theNode.Points
x = arr(1, 1)
y = arr(1, 2)
OUTPUT tabs(tabCount) & "points : [ " & x & ", " & y & " ],"
tabCount = tabCount - 1
OUTPUT tabs(tabCount) & "},"
Set theNode = Nothing
Next i
tabCount = tabCount - 1
OUTPUT tabs(tabCount) & "], // nodes"
End If
tabCount = tabCount - 1
If isChild Then
OUTPUT tabs(tabCount) & "}, // groupItems[ """ & theShape.id & """ ]"
Else
OUTPUT tabs(tabCount) & "} // shapez[ """ & theShape.id & """ ]"
OUTPUT tabs(tabCount) & "shapes.push( shapez[ """ & theShape.id & """ ] );"
OUTPUT tabs(tabCount) & "loadedShape = shapez[ """ & theShape.id & """ ];"
End If
End Sub ' outputShapeToJS
'=== 小物
Sub OUTPUT(text As String)
OUT.WriteText text, 1 ' 1 で改行
End Sub
Function tabs(count)
tabs = String(count, vbTab)
End Function
'=== UTLs local
Sub debug_array(ByRef theArray)
Debug.Print "--- debug_array() ---"
Debug.Print LBound(theArray) & "~" & UBound(theArray)
Dim i As Integer
For i = 0 To UBound(theArray)
Debug.Print i, theArray(i).Name
Next i
Debug.Print "---/debug_array() ---"
End Sub
Sub checkShape(theShape)
If theShape.Type = msoGroup Then
Dim childShape As Shape
For Each childShape In theShape.groupItems
If childShape.Nodes.count = 0 Then
childShape.Select
MsgBox "この図形は node がないので変換できません"
End
End If
Next
Else
If theShape.Nodes.count = 0 Then
theShape.Select
MsgBox "この図形は node がないので変換できません"
End
End If
End If
End Sub
'=== UTLs global (special)
Sub duplicate(theShape As Shape)
' 3種類のエラーを検出する。
' paste pasteメソッド実行時のエラーは on error で retry する。
' otori copyメソッドが遅れた場合、pasteされたものは事前データ(おとり)であることを検出し goto で retry する。
' extra コピーした結果の図形の名前がコピー元と異なる場合 goto で retry する。
' 正規表現 雑用
Dim re As Object
Dim theMatches As Variant
' エラー検出用
Dim baseName As String
Set re = CreateObject("VBScript.RegExp") '''
re.Pattern = "(.+) (\d+)$"
Set theMatches = re.Execute(theShape.Name) '''
If theMatches.count > 0 Then
baseName = theMatches(0).subMatches(0)
End If
Set re = Nothing
Set theMatches = Nothing
' デバッグ用
Dim tryCount: tryCount = 0
Dim errorLogs As String: errorLogs = ""
Dim errorLog As String: errorLog = ""
'theShape.BottomRightCell.Select
theShape.TopLeftCell.Select
On Error GoTo LabelRetry ' [paste] paste メソッドでエラーしたら retry する
LabelRetry:
tryCount = tryCount + 1
'check. デバッグ時の無限ループ防止用
If tryCount > 100 Then
Debug.Print "エラー過多"
Exit Sub
End If
errorLogs = errorLogs & errorLog
' おとり
Dim rect As Shape
Set rect = ActiveSheet.shapes.AddShape(msoShapeRectangle, 100, 100, 100, 100)
rect.Name = "__otori__"
rect.Cut
theShape.Copy
DoEvents
Application.Wait [Now()] + 70 / 86400000
errorLog = "paste, "
ActiveSheet.Paste '★★★
'check. [otori] おとりを貼り付けした
If Selection.ShapeRange(1).Name = "__otori__" Then
Selection.Delete
errorLog = "otori, "
GoTo LabelRetry
End If
'check. [extra] 以前コピーされた図形を貼り付けした
If baseName <> "" Then
Set re = CreateObject("VBScript.RegExp") '''
re.Pattern = "^" & baseName & " (\d+)$"
Set theMatches = re.Execute(Selection.ShapeRange(1).Name) '''
Dim theMatchesCount
theMatchesCount = theMatches.count
Set re = Nothing
Set theMatches = Nothing
If theMatchesCount = 0 Then
Selection.Delete
errorLog = "extra, "
GoTo LabelRetry
End If
End If
On Error GoTo 0
'debug.
If tryCount > 1 Then
Debug.Print "◆◆◆duplicate tryCount " & (tryCount - 1) & " (" & errorLogs & ") at " & theShape.Name
End If
'debug.
Selection.ShapeRange(1).Name = Selection.ShapeRange(1).Name
End Sub
Sub bugfixShape(theShape As Shape, Optional ByRef idListStringP As String = "")
If theShape.Type = msoGroup Then
' グループ
' バグを持っているか
Dim hasBug As Boolean
hasBug = False
Dim groupItem As Shape
For Each groupItem In theShape.groupItems
'check グループ内の子なのに child が 0(バグ)
If groupItem.child = 0 Then
hasBug = True
GoTo label_bugfixShapeBreak1
End If
Next groupItem
label_bugfixShapeBreak1:
' バグを持っている場合は、
If hasBug Then
Dim item As Shape ' for用
Dim nameBak As String
nameBak = theShape.Name
Dim idBak As String
idBak = theShape.id
' グループを解除し、
Dim ungroupedShapes As ShapeRange
Set ungroupedShapes = theShape.Ungroup 'ungroup'''
Dim idListStringC As String
For Each item In ungroupedShapes
idListStringC = idListStringC & "," & item.id
Next item
'check.
idListStringC = idListStringC & ","
' 入れ子なら再帰する。
For Each item In ungroupedShapes
If item.Type = msoGroup Then
bugfixShape item, idListStringC
End If
Next item
Set ungroupedShapes = Nothing
' 再グループ化する
Dim ids() As String
ids = Split(idListStringC, ",")
Dim id As Variant
For Each id In ids
If id <> "" Then
For Each item In ActiveSheet.shapes
If item.id = id Then
item.Select False
GoTo label_bugfixShapeBreak2
End If
Next item
End If
label_bugfixShapeBreak2:
Next id
Set theShape = Selection.ShapeRange.Group ' これは解放しなくてよい
'check.
idListStringP = Replace(idListStringP, "," & idBak & ",", "," & theShape.id & ",")
theShape.Name = nameBak
End If
End If
theShape.Select Replace:=False
End Sub
'=== UTLs global
Sub selectShapes(ByRef shapes)
shapes(0).TopLeftCell.Select
Dim i As Integer
For i = LBound(shapes) To UBound(shapes)
If shapes(i).child Then
shapes(i).ParentGroup.Select Replace:=False
Else
shapes(i).Select Replace:=False
End If
Next i
End Sub
Function groupToArray(theShape As Shape) As Shape()
'グループが含む子を単純に(入れ子を考慮せず)配列にする
'check.
If Not theShape.Type = msoGroup Then
MsgBox "groupToArray()、引数はグループではない"
Return
End If
Dim children() As Shape
ReDim children(theShape.groupItems.count - 1)
Dim i As Integer
For i = 0 To theShape.groupItems.count - 1
Set children(i) = theShape.groupItems(i + 1)
Next i
groupToArray = children
End Function